
/*=====================================================================

  File:      FileStoreProvider.cs
  Summary:   SQL Remote Blob Storage sample provider. This file contains
             the basic implementation of a provider that uses NTFS files
             as the blob store.
  Date:	     June 24th, 2008

---------------------------------------------------------------------

  This file is part of the Microsoft SQL Server Code Samples.
  Copyright (C) Microsoft Corporation.  All rights reserved.

This source code is intended only as a supplement to Microsoft
Development Tools and/or on-line documentation.  See these other
materials for detailed information regarding Microsoft code samples.

THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.

======================================================= */

using System;
using System.IO;
using System.Text;
using System.Collections;
using System.Collections.Generic;
using System.Security.AccessControl;
using System.Globalization;

using Microsoft.Data.SqlRemoteBlobs;
using Microsoft.Data.BlobStores;

using System.Reflection;
using System.Runtime.CompilerServices;
using System.Diagnostics.CodeAnalysis;

namespace Microsoft.Data.BlobStores.FileBlobStore
{
    /// <summary>
    /// Implementation of a store library that uses ntfs files
    /// </summary>
    public
    sealed
    class FileStoreLibrary : BlobStore
    {
        //--------------------------------------------------------------------+
        //                                                                    |
        #region Initialization

        /// <summary>
        /// Default Constructor
        /// </summary>
        public FileStoreLibrary()
            : base()
        {
        }

        /// <summary>
        /// Initialization method for the blob store object.
        /// This is called once per object before any other operation is performed on it.
        /// </summary>
        /// <param name="commonConfiguration">
        /// Configuration information that is understood by RBS client library.
        /// ConfigItems present are: BackEndBlobStoreVersion and BlobStoreLocation.
        /// </param>
        /// <param name="coreConfiguration">
        /// Configuration information that is specific to the provider.
        /// "Core" configuration consists of configuration that is required to access
        /// existing blobs in the backend blob store.
        /// This is optional and present only if specified in the blob store entry in the database.
        /// </param>
        /// <param name="extendedConfiguration">
        /// Configuration information that is specific to the provider.
        /// without having access to the database controlling the blob.
        /// "Extended" configuration consists of configuration that is not needed to access
        /// existing blobs, but is needed for other operations, such as create pool, store blob etc.
        /// This is optional and may not be present. This is because extended configuration
        /// is not included in Blob Locators, which can be used to access a blob.
        /// </param>
        /// <param name="credentials"> 
        /// Optional credentials to be used to connect to the backend blob store.
        /// </param>
        /// <exception cref="BlobStoreException">
        /// Allowed exception codes are: AccessDenied, ConfigurationMissing.
        /// </exception>
        public 
        override
        void Initialize(
            ConfigItemList commonConfiguration,
            ConfigItemList coreConfiguration,
            ConfigItemList extendedConfiguration,
            BlobStoreCredentials[] credentials)
        {
            if (commonConfiguration == null) throw new ArgumentNullException("commonConfiguration");

            m_BaseDirectory = commonConfiguration.BlobStoreLocation;

            // Check the configuration passed in for required parameters.
            //
            if (m_BaseDirectory == null)
            {
                throw new BlobStoreException(BlobStoreExceptionCode.ConfigurationMissing,
                    FileBlobStore.Resources.FileStoreConfigurationMissingError, commonConfiguration.ToString(), "BlobStoreLocation");
            }

            if (credentials != null && credentials.Length > 0 && credentials[0] != null)
            {
                // Credentials have been passed in. Set up impersonation object.
                // For now, just use the first credential that gets passed in.
                //
                m_Impersonation = new Impersonation(credentials[0]);
            }

            // The pool_capacity config key is optional so a default value is used
            // if it's not present.
            //
            if (extendedConfiguration != null &&
                extendedConfiguration.Exists(FileBlobStore.Resources.FileProviderPoolCapacityKey))
            {
                m_PoolCapacity = Convert.ToInt32(extendedConfiguration.GetItem(
                    FileBlobStore.Resources.FileProviderPoolCapacityKey).Value, CultureInfo.InvariantCulture);
            }
            else
            {
                m_PoolCapacity = 100;
            }
        }

        /// <summary>
        /// Dispose function for subclasses to implement.
        /// This is where the bulk of the clean up code should be implemented.
        /// </summary>
        protected
        override
        void Dispose(bool disposing)
        {
            if (!m_Disposed)
            {
                if (disposing)
                {
                    // Free managed resources
                    //
                    if (m_Impersonation != null)
                    {
                        m_Impersonation.Dispose();
                    }
                }

                // Free native resources.
                // This will be run in the finalizer case as well as explicit dispose.
                //
                m_Disposed = true;
            }
            base.Dispose(disposing);
        }

        #endregion
        

        //--------------------------------------------------------------------+
        //                                                                    |
        #region Pool related members

        /// <summary>
        /// Creates a new pool in the blob store.
        /// </summary>
        /// <param name="configuration">
        /// Optional configuration information to be associated with this pool.
        /// Possible items are: IsExpungeEnabled.
        /// </param>
        /// <returns>
        /// StorePoolId of the pool just created.
        /// If OptimizationSpecifiedIds is true, it must have a length 
        /// less than or equal to 16 bytes.
        /// </returns>
        /// <exception cref="BlobStoreException">
        /// Allowed exception codes are: AccessDenied, ConfigurationDoesNotAllowOperation, NoMoreSpace, 
        /// OperationFailedAuthoritative
        /// </exception>
        public
        override
        byte[] CreatePool(ConfigItemList configuration)
        {
            byte[] poolId;

            // Create a new pool with a new guid
            //
            Guid storePool = Guid.NewGuid();
            poolId = storePool.ToByteArray();

            // Create the pool.
            //
            string fullPath = Path.Combine(m_BaseDirectory, storePool.ToString());

            // If a credential has been specified, begin impersonation for this operation.
            //
            if (m_Impersonation != null) m_Impersonation.BeginImpersonation();
            try
            {
                if (!Directory.Exists(fullPath))
                {
                    DebugPrint.WriteLine(OperationType.BlobStoreCreatePool, LogLevel.Information, FileBlobStore.Resources.CreatingPoolTrace, storePool);
                    Directory.CreateDirectory(fullPath);
                } else
                {
                    // If the directory exists, two guids created by Guid.NewGuid were identical.
                    // This is very, very unlikely.
                    //
                    throw new BlobStoreException(BlobStoreExceptionCode.OperationFailedAuthoritative,
                        FileBlobStore.Resources.PoolCouldNotBeCreatedError);
                }
            }
            catch (System.UnauthorizedAccessException ex)
            {
                throw new BlobStoreException(BlobStoreExceptionCode.AccessDenied, ex, FileBlobStore.Resources.AccessDeniedError, fullPath);
            }
            catch (System.Security.SecurityException ex)
            {
                throw new BlobStoreException(BlobStoreExceptionCode.AccessDenied, ex, FileBlobStore.Resources.SecurityExceptionError, fullPath);
            }
            catch (System.IO.IOException ex)
            {
                throw new BlobStoreException(BlobStoreExceptionCode.AccessDenied, ex, FileBlobStore.Resources.FileIOError, fullPath);
            }
            finally
            {
                if (m_Impersonation != null) m_Impersonation.EndImpersonation();
            }

            return poolId;
        }

        /// <summary>
        /// Deletes a pool in the blob store if it is empty (no blobs are present in the pool).
        /// </summary>
        /// <param name="storePoolId"> The StorePoolId of the pool to delete. </param>
        /// <exception cref="BlobStoreException">
        /// Allowed exception codes are: AccessDenied, ConfigurationDoesNotAllowOperation, 
        /// PoolNotFound, PoolNotEmpty, OperationFailedAuthoritative, OperationFailedMaybe.
        /// </exception>
        public 
        override 
        void DeletePool(byte[] storePoolId)
        {
            DebugPrint.WriteLine(OperationType.BlobStoreDeletePool, LogLevel.Information,
                FileBlobStore.Resources.EnterDeletePoolTrace);

            // Recreate the pool id.
            //
            Guid storePool = new Guid(storePoolId);

            // If a credential has been specified, begin impersonation for this operation.
            //
            if (m_Impersonation != null) m_Impersonation.BeginImpersonation();
            try
            {
                // Checks the pool exists and is accessible.
                //
                string poolLocation = CheckPoolExists(storePoolId, false);

                // Start an enumeration of the pool. If this returns more than 0 results then cancel the 
                // deletion.
                // 
                object poolEnum = BeginEnumerateBlobs(storePoolId);
                try
                {
                    if (EnumerateBlobs(poolEnum, 1).Length != 0)
                    {
                        throw new BlobStoreException(BlobStoreExceptionCode.PoolNotEmpty,
                            FileBlobStore.Resources.PoolNotEmptyError, storePool);
                    }
                }
                finally
                {
                    EndEnumerateBlobs(poolEnum);
                }

                // Delete the pool directory.
                //
                try
                {
                    Directory.Delete(poolLocation);
                    DebugPrint.WriteLine(OperationType.BlobStoreDeletePool, LogLevel.Information,
                        FileBlobStore.Resources.DeletePoolTrace, storePool);
                }
                catch (System.UnauthorizedAccessException ex)
                {
                    throw new BlobStoreException(BlobStoreExceptionCode.AccessDenied, ex, FileBlobStore.Resources.AccessDeniedError, poolLocation);
                }
                catch (System.IO.DirectoryNotFoundException ex)
                {
                    throw new BlobStoreException(BlobStoreExceptionCode.PoolNotFound, ex, FileBlobStore.Resources.PoolNotFoundError, storePool);
                }
                catch (System.IO.IOException ex)
                {
                    throw new BlobStoreException(BlobStoreExceptionCode.OperationFailedAuthoritative, ex, FileBlobStore.Resources.OperationFailedAuthoritativeError);
                }
            }
            finally
            {
                if (m_Impersonation != null) m_Impersonation.EndImpersonation();
            }
        }

        /// <summary>
        /// Begins enumeration of all the blobs in the given pool.
        /// No blob information is returned here. Only a ResumeHandle is
        /// returned, which is then used to enumerate the blobs a few at a time.
        /// </summary>
        /// <param name="storePoolId"> The pool to enumerate. </param>
        /// <returns> 
        /// ResumeHandle. This object is passed to subsequent EnumerateBlobs calls to
        /// enumerate a few blobs at a time.
        /// </returns>
        /// <exception cref="BlobStoreException">
        /// Allowed exception codes are: AccessDenied, ConfigurationDoesNotAllowOperation, 
        /// PoolNotFound, OperationFailedAuthoritative.
        /// </exception>
        public 
        override 
        object BeginEnumerateBlobs(byte[] storePoolId)
        {
            // Recreate the pool id.
            //
            FileBlockProcessor blockProcessor;
            Guid storePool = new Guid(storePoolId);

            DebugPrint.WriteLine(OperationType.BlobStoreBeginEnumBlobs, LogLevel.Information,
                FileBlobStore.Resources.BeginEnumerateBlobsTrace, storePool);

            // If a credential has been specified, begin impersonation for this operation.
            //
            if (m_Impersonation != null) m_Impersonation.BeginImpersonation();
            try
            {
                // Check the pool path exists and is accessible.
                //
                string fullPath = CheckPoolExists(storePoolId, false);
                try
                {
                    DebugPrint.WriteLine(OperationType.BlobStoreBeginEnumBlobs, LogLevel.Information, FileBlobStore.Resources.FoundPoolTrace, storePool);

                    // Create a FileBlobProcessor object to track the enumeration.
                    //
                    blockProcessor = new FileBlockProcessor(fullPath);
                }
                catch (System.UnauthorizedAccessException ex)
                {
                    throw new BlobStoreException(BlobStoreExceptionCode.AccessDenied, ex, FileBlobStore.Resources.AccessDeniedError, fullPath);
                }
                catch (System.Security.SecurityException ex)
                {
                    throw new BlobStoreException(BlobStoreExceptionCode.AccessDenied, ex, FileBlobStore.Resources.SecurityExceptionError, fullPath);
                }
                catch (System.IO.IOException ex)
                {
                    throw new BlobStoreException(BlobStoreExceptionCode.OperationFailedAuthoritative, ex, FileBlobStore.Resources.OperationFailedAuthoritativeError);
                }
            }
            finally
            {
                if (m_Impersonation != null) m_Impersonation.EndImpersonation();
            }

            return (object)blockProcessor;
        }

        /// <summary>
        /// Enumerates blobs as requested in the EnumerateBlobs() call that originally
        /// returned this resumeHandle.
        /// Enumerates the next set of blobs, returning upto maxBlobs number of blobs.
        /// Must return maxBlobs number of blobs if there are at least maxBlobs blobs 
        /// still remaining to be enumerated.
        /// Returning less than maxBlobs blobs (including 0 blobs) indicates that there
        /// are no more blobs left to enumerate after this set.
        /// </summary>
        /// <param name="resumeHandle">
        /// The ResumeHandle can be used by the BlobStore to keep state information 
        /// related to the enumeration.
        /// </param>
        /// <param name="maxBlobs"> Maximum number of blobs to enumerate. </param>
        /// <returns> 
        /// Array of BlobInformation. Each element contains information about one blob.
        /// The elements in the array are in the required order if ths is a 
        /// sorted enumeration.
        /// </returns>
        /// <exception cref="BlobStoreException">
        /// Allowed exception codes are: OperationFailedAuthoritative.
        /// </exception>
        public
        override
        BlobInformation[] EnumerateBlobs(object resumeHandle, int maxBlobs)
        {
            FileBlockProcessor blockProcessor = (FileBlockProcessor)resumeHandle;
            List<BlobInformation> returnInfo = new List<BlobInformation>();

            DebugPrint.WriteLine(OperationType.BlobStoreEnumBlobs, LogLevel.Verbose,
                FileBlobStore.Resources.EnumerateBlobsTrace, maxBlobs);

            // If a credential has been specified, begin impersonation for this operation.
            //
            if (m_Impersonation != null) m_Impersonation.BeginImpersonation();
            try
            {
                // Process each of the returned FileInfo records for this enumeration chunk.
                //
                foreach (FileInfo fileInfo in blockProcessor.GetNextFileBlock(maxBlobs))
                {
                    BlobInformation info = new BlobInformation();
                    byte[] storePoolId;
                    byte[] storeBlobId;

                    // This may throw if the filename is not in the correct format, ie. there 
                    // are other random files in the directory.
                    //
                    try
                    {
                        ExtractIds(fileInfo.FullName, out storePoolId, out storeBlobId);

                        info.StoreBlobId = storeBlobId;
                        info.StoreBlobCreateTime = fileInfo.CreationTimeUtc;
                        info.BlobLength = fileInfo.Length;

                        returnInfo.Add(info);
                    }
                    catch (FormatException)
                    {
                        DebugPrint.WriteLine(OperationType.BlobStoreEnumBlobs, LogLevel.Error,
                            FileBlobStore.Resources.FileNotRecognizedError, fileInfo.FullName);
                    }
                }
            }
            finally
            {
                if (m_Impersonation != null) m_Impersonation.EndImpersonation();
            }

            return returnInfo.ToArray();
        }

        /// <summary>
        /// Ends an enumeration of a pool.
        /// This will be called only after a successful call to one of the
        /// BeginEnumerateBlobs() methods.
        /// 0 or more EnumerateBlobs() calls may have been made and 
        /// none, some or all qualifying blobs may have been consumed before 
        /// this method is called to end the enumeration.
        /// </summary>
        /// <param name="resumeHandle">
        /// The ResumeHandle associated with this enumeration.
        /// The BlobStore can cleanup/release its state related to this enumeration.
        /// </param>
        /// <exception cref="BlobStoreException">
        /// Allowed exception codes are: None.
        /// </exception>
        public 
        override 
        void EndEnumerateBlobs(object resumeHandle)
        {
            DebugPrint.WriteLine(OperationType.BlobStoreEndEnumBlobs, LogLevel.Information,
                FileBlobStore.Resources.EndEnumerateBlobsTrace);

            if (resumeHandle != null)
            {
                FileBlockProcessor blockProcessor = (FileBlockProcessor)resumeHandle;
                blockProcessor.Close();
            }
        }

        #endregion
        

        //--------------------------------------------------------------------+
        //                                                                    |
        #region Blob related members

        /// <summary>
        /// Creates a new blob - push method.
        /// The Blob Store is free to pick a StoreBlobId for this blob,
        /// upto 255 bytes long. 
        /// Shorter IDs are preferred and may lead to better performance.
        /// Returns a BlobStoreWriterStream to which blob data will be written
        /// by the calling application.
        /// The blob needs to be committed later when the calling application 
        /// calls BlobStoreWriterStream.Commit().
        /// </summary>
        /// <param name="storePoolId"> The pool in which to create a new blob. </param>
        /// <returns> 
        /// A BlobStoreWriterStream object to which blob data will be written. 
        /// </returns>
        /// <exception cref="BlobStoreException">
        /// Allowed exception codes are: AccessDenied, ConfigurationDoesNotAllowOperation, 
        /// PoolNotFound, NoMoreSpace, OperationFailedAuthoritative.
        /// </exception>
        public
        override
        BlobStoreWriterStream CreateNewBlob(byte[] storePoolId)
        {
            DebugPrint.WriteLine(OperationType.BlobStoreNewBlob, LogLevel.Information,
                FileBlobStore.Resources.FileStoreEnterProviderCreateBlobTrace);

            if (storePoolId == null) throw new ArgumentNullException("storePoolId");

            // If a credential has been specified, begin impersonation for this operation.
            //
            if (m_Impersonation != null) m_Impersonation.BeginImpersonation();
            try
            {
                // Confirm access to the pool path.
                //
                string path = CheckPoolExists(storePoolId, true);
                try
                {
                    // Open a new file and output the input stream to it.
                    //
                    Guid filenameGuid = Guid.NewGuid();
                    string blobAddress = filenameGuid.ToString() + ".rbsblob";

                    path = Path.Combine(path, blobAddress);

                    BlobInformation blobInformation = new BlobInformation();

                    blobInformation.StoreBlobId = filenameGuid.ToByteArray();

                    FileStoreWriterStream outStream = new FileStoreWriterStream(path, blobInformation);

                    return outStream;
                }
                catch (System.UnauthorizedAccessException ex)
                {
                    throw new BlobStoreException(BlobStoreExceptionCode.AccessDenied, ex, FileBlobStore.Resources.AccessDeniedError, path);
                }
                catch (System.Security.SecurityException ex)
                {
                    throw new BlobStoreException(BlobStoreExceptionCode.AccessDenied, ex, FileBlobStore.Resources.SecurityExceptionError, path);
                }
                catch (System.IO.IOException ex)
                {
                    throw new BlobStoreException(BlobStoreExceptionCode.OperationFailedAuthoritative, ex, FileBlobStore.Resources.OperationFailedAuthoritativeError);
                }
            }
            finally
            {
                if (m_Impersonation != null) m_Impersonation.EndImpersonation();
            }
        }

        /// <summary>
        /// Creates a new blob - pull method.
        /// The Blob Store is free to pick a StoreBlobId for this blob,
        /// upto 255 bytes long. 
        /// Shorter IDs are preferred and may lead to better performance.
        /// Takes blob data from the provided inStream and stores it in a 
        /// new blob on the backend blob store.
        /// Successful completion of this method indicates that the blob
        /// is durably stored on the backed blob store.
        /// </summary>
        /// <param name="storePoolId"> The pool in which to create a new blob. </param>
        /// <param name="inStream">
        /// The data to store in the new blob is provided by this stream.
        /// This is a readable stream with CanRead set to true.
        /// No assumptions should be made about whether or not the stream 
        /// supports seek.
        /// The stream must be closed by the BlobStore after all data has been read from it.
        /// </param>
        /// <returns> 
        /// Information about the stored blob. 
        /// </returns>
        /// <exception cref="BlobStoreException">
        /// Allowed exception codes are: AccessDenied, ConfigurationDoesNotAllowOperation, 
        /// PoolNotFound, NoMoreSpace, OperationFailedAuthoritative.
        /// </exception>
        public
        override
        BlobInformation CreateNewBlobFromStream(byte[] storePoolId, Stream inStream)
        {
            DebugPrint.WriteLine(OperationType.BlobStoreNewBlobFromStream, LogLevel.Information,
                FileBlobStore.Resources.FileStoreEnterProviderStoreBlobTrace);

            if (storePoolId == null) throw new ArgumentNullException("storePoolId");

            // If a credential has been specified, begin impersonation for this operation.
            //
            if (m_Impersonation != null) m_Impersonation.BeginImpersonation();
            try
            {
                // Confirm access to the pool path.
                //
                string outputPath = CheckPoolExists(storePoolId, true);
                try
                {
                    // Open a new file and output the input stream to it.
                    //
                    Guid filenameGuid = Guid.NewGuid();
                    string blobAddress = filenameGuid.ToString() + ".rbsblob";

                    outputPath = Path.Combine(outputPath, blobAddress);
                    Utilities.WriteStreamToFile(inStream, outputPath);

                    BlobInformation blobInformation = new BlobInformation();

                    blobInformation.StoreBlobId = filenameGuid.ToByteArray();

                    // Open outputPath again to retrieve blob size and length.
                    //
                    FileInfo fi = new FileInfo(outputPath);
                    blobInformation.StoreBlobCreateTime = fi.CreationTimeUtc;
                    blobInformation.BlobLength = fi.Length;

                    return blobInformation;
                }
                catch (System.UnauthorizedAccessException ex)
                {
                    throw new BlobStoreException(BlobStoreExceptionCode.AccessDenied, ex, FileBlobStore.Resources.AccessDeniedError, outputPath);
                }
                catch (System.Security.SecurityException ex)
                {
                    throw new BlobStoreException(BlobStoreExceptionCode.AccessDenied, ex, FileBlobStore.Resources.SecurityExceptionError, outputPath);
                }
                catch (System.IO.IOException ex)
                {
                    throw new BlobStoreException(BlobStoreExceptionCode.OperationFailedAuthoritative, ex, FileBlobStore.Resources.OperationFailedAuthoritativeError);
                }
            }
            finally
            {
                if (m_Impersonation != null) m_Impersonation.EndImpersonation();
            }
        }

        /// <summary>
        /// This method is called to read the data in a committed blob - pull method.
        /// </summary>
        /// <param name="storePoolId"> The pool to which the blob belongs. </param>
        /// <param name="storeBlobId"> The ID of the blob to be read. </param>
        /// <returns>
        /// A readable stream derived from System.IO.Stream.
        /// CanRead and CanSeek should be true and CanWrite should be false.
        /// The application may read data from this stream and after it is done with it,
        /// the application needs to call Close().
        /// </returns>
        /// <exception cref="BlobStoreException">
        /// Allowed exception codes are: AccessDenied, ConfigurationDoesNotAllowOperation, 
        /// PoolNotFound, BlobNotFound, OperationFailedAuthoritative.
        /// </exception>
        public
        override
        Stream ReadBlob(byte[] storePoolId, byte[] storeBlobId)
        {
            DebugPrint.WriteLine(OperationType.BlobStoreReadBlob, LogLevel.Information,
                FileBlobStore.Resources.FileStoreEnterProviderReadBlobTrace);

            if (storePoolId == null) throw new ArgumentNullException("storePoolId");
            if (storeBlobId == null) throw new ArgumentNullException("storeBlobId");

            // If a credential has been specified, begin impersonation for this operation.
            //
            if (m_Impersonation != null) m_Impersonation.BeginImpersonation();
            try
            {
                string blobLocation = CheckBlobExists(storePoolId, storeBlobId);

                try
                {
                    return new FileStream(blobLocation, FileMode.Open, FileAccess.Read);
                }
                catch (System.IO.FileNotFoundException ex)
                {
                    throw new BlobStoreException(BlobStoreExceptionCode.BlobNotFound, ex, FileBlobStore.Resources.FileNotFoundError, blobLocation);
                }
                catch (System.UnauthorizedAccessException ex)
                {
                    throw new BlobStoreException(BlobStoreExceptionCode.AccessDenied, ex, FileBlobStore.Resources.AccessDeniedError, blobLocation);
                }
                catch (System.Security.SecurityException ex)
                {
                    throw new BlobStoreException(BlobStoreExceptionCode.AccessDenied, ex, FileBlobStore.Resources.SecurityExceptionError, blobLocation);
                }
                catch (System.IO.IOException ex)
                {
                    throw new BlobStoreException(BlobStoreExceptionCode.OperationFailedAuthoritative, ex, FileBlobStore.Resources.OperationFailedAuthoritativeError);
                }
            }
            finally
            {
                if (m_Impersonation != null) m_Impersonation.EndImpersonation();
            }
        }

        /// <summary>
        /// This method is called to read the data in a committed blob - push method.
        /// </summary>
        /// <param name="storePoolId"> The pool to which the blob belongs. </param>
        /// <param name="storeBlobId"> The ID of the blob to be read. </param>
        /// <param name="outStream">
        /// The stream into which to write the blob data.
        /// This is a writable stream with CanWrite set to true.
        /// No assumptions should be made about whether or not the stream 
        /// supports seek and read.
        /// The stream must be closed by the BlobStore after all data has been written to it.
        /// </param>
        /// <exception cref="BlobStoreException">
        /// Allowed exception codes are: AccessDenied, ConfigurationDoesNotAllowOperation, 
        /// PoolNotFound, BlobNotFound, OperationFailedAuthoritative.
        /// </exception>
        public override 
        void ReadBlobIntoStream(byte[] storePoolId, byte[] storeBlobId, Stream outStream)
        {
            DebugPrint.WriteLine(OperationType.BlobStoreReadBlob, LogLevel.Information,
                FileBlobStore.Resources.FileStoreEnterProviderReadBlobIntoStreamTrace);

            if (storePoolId == null) throw new ArgumentNullException("storePoolId");
            if (storeBlobId == null) throw new ArgumentNullException("storeBlobId");

            // If a credential has been specified, begin impersonation for this operation.
            //
            if (m_Impersonation != null) m_Impersonation.BeginImpersonation();
            try
            {
                string blobLocation = CheckBlobExists(storePoolId, storeBlobId);

                try
                {
                    FileStream inputStream = new FileStream(blobLocation, FileMode.Open);

                    Utilities.ReadWriteStream(inputStream, outStream);

                    inputStream.Close();

                    // Don't close the output stream in case the user wants to manipulate it somehow
                    // after it is read.
                }
                catch (System.IO.FileNotFoundException ex)
                {
                    throw new BlobStoreException(BlobStoreExceptionCode.BlobNotFound, ex, FileBlobStore.Resources.FileNotFoundError, blobLocation);
                }
                catch (System.UnauthorizedAccessException ex)
                {
                    throw new BlobStoreException(BlobStoreExceptionCode.AccessDenied, ex, FileBlobStore.Resources.AccessDeniedError, blobLocation);
                }
                catch (System.Security.SecurityException ex)
                {
                    throw new BlobStoreException(BlobStoreExceptionCode.AccessDenied, ex, FileBlobStore.Resources.SecurityExceptionError, blobLocation);
                }
                catch (System.IO.IOException ex)
                {
                    throw new BlobStoreException(BlobStoreExceptionCode.OperationFailedAuthoritative, ex, FileBlobStore.Resources.OperationFailedAuthoritativeError);
                }
            }
            finally
            {
                if (m_Impersonation != null) m_Impersonation.EndImpersonation();
            }
        }

        /// <summary>
        /// Delete the specified blob.
        /// </summary>
        /// <param name="storePoolId"> The pool to which the blob belongs. </param>
        /// <param name="storeBlobId"> The ID of the blob to be deleted. </param>
        /// <exception cref="BlobStoreException">
        /// Allowed exception codes are: AccessDenied, ConfigurationDoesNotAllowOperation, 
        /// PoolNotFound, BlobNotFound, BlobInUse, OperationFailedAuthoritative, OperationFailedMaybe.
        /// </exception>
        public
        override
        void DeleteBlob(byte[] storePoolId, byte[] storeBlobId)
        {
            DebugPrint.WriteLine(OperationType.BlobStoreDeleteBlob, LogLevel.Information,
                FileBlobStore.Resources.EnterDeleteBlobTrace);

            if (storePoolId == null) throw new ArgumentNullException("storePoolId");
            if (storeBlobId == null) throw new ArgumentNullException("storeBlobId");

            // If a credential has been specified, begin impersonation for this operation.
            //
            if (m_Impersonation != null) m_Impersonation.BeginImpersonation();
            try
            {
                string blobLocation = CheckBlobExists(storePoolId, storeBlobId);

                try
                {
                    File.Delete(blobLocation);
                    DebugPrint.WriteLine(OperationType.BlobStoreDeleteBlob, LogLevel.Information,
                        FileBlobStore.Resources.DeleteBlobTrace, blobLocation);
                }
                catch (System.UnauthorizedAccessException ex)
                {
                    throw new BlobStoreException(BlobStoreExceptionCode.AccessDenied, ex, FileBlobStore.Resources.AccessDeniedError, blobLocation);
                }
                catch (System.IO.DirectoryNotFoundException ex)
                {
                    throw new BlobStoreException(BlobStoreExceptionCode.BlobNotFound, ex, FileBlobStore.Resources.BlobNotFoundError, blobLocation);
                }
                catch (System.IO.IOException ex)
                {
                    throw new BlobStoreException(BlobStoreExceptionCode.OperationFailedAuthoritative, ex, FileBlobStore.Resources.OperationFailedAuthoritativeError);
                }
            }
            finally
            {
                if (m_Impersonation != null) m_Impersonation.EndImpersonation();
            }
        }

        #endregion
        

        //--------------------------------------------------------------------+
        //                                                                    |
        #region Internal methods

        /// <summary>
        /// Runs checks to ensure the current pool exists and is accessible.
        /// </summary>
        /// <param name="storePoolId">
        /// Store pool id in bytes.
        /// </param>
        /// <param name="checkSpace">
        /// Forces verification if the pool can store additional blobs.
        /// </param>
        /// <returns>
        /// Full path to the verified pool.
        /// </returns>
        /// <exception cref="BlobStoreException">
        /// Allowed exception codes are: AccessDenied, PoolNotFound.
        /// </exception>
        private
        string
        CheckPoolExists(byte[] storePoolId, bool checkSpace)
        {
            string poolLocation = ExtractPoolPath(storePoolId);

            // Check to see the pool directory exists.
            //
            if (!Directory.Exists(poolLocation))
            {
                throw new BlobStoreException(BlobStoreExceptionCode.PoolNotFound,
                    FileBlobStore.Resources.PoolNotFoundError, new Guid(storePoolId));
            }

            DirectoryInfo poolDirectory = new DirectoryInfo(poolLocation);

            if (checkSpace && poolDirectory.GetFiles().Length >= m_PoolCapacity)
            {
                throw new BlobStoreException(BlobStoreExceptionCode.NoMoreSpace,
                    FileBlobStore.Resources.PoolCannotStoreMoreBlobsError, new Guid(storePoolId));
            }

            // Check to see if the pool directory is currently accessible.
            //
            CheckAccessToDirectory(poolLocation);

            return poolLocation;
        }

        /// <summary>
        /// Runs checks to ensure the current blob exists and is accessible.
        /// </summary>
        /// <param name="storePoolId">
        /// Store pool id in bytes.
        /// </param>
        /// <param name="storeBlobId">
        /// Store blob id in bytes.
        /// </param>
        /// <returns>
        /// Full path to the verified blob.
        /// </returns>
        /// <exception cref="BlobStoreException">
        /// Allowed exception codes are: AccessDenied, PoolNotFound, BlobNotFound.
        /// </exception>
        private
        string
        CheckBlobExists(byte[] storePoolId, byte[] storeBlobId)
        {
            string poolLocation = CheckPoolExists(storePoolId, false);
            string blobLocation = ExtractBlobPath(storePoolId, storeBlobId);

            // Check to see the blob file exists.
            //
            if (!File.Exists(blobLocation))
            {
                throw new BlobStoreException(BlobStoreExceptionCode.BlobNotFound,
                    FileBlobStore.Resources.BlobNotFoundError, blobLocation);
            }

            // Check to see if the pool directory is currently accessible.
            //
            CheckAccessToFile(blobLocation);

            return blobLocation;
        }

        /// <summary>
        /// Extracts the path of the pool from the storePoolId and this 
        /// store's base directory.
        /// </summary>
        private
        string
        ExtractPoolPath(byte[] storePoolId)
        {
            if (storePoolId == null) throw new ArgumentNullException("storePoolId");

            // Extract the Store Pool Id and return the combined path.
            //
            Guid poolId = new Guid(storePoolId);
            return Path.Combine(m_BaseDirectory, poolId.ToString());
        }

        /// <summary>
        /// Extracts the path of the blob from the ConfigItemList and this store's base directory
        /// </summary>
        private
        string
        ExtractBlobPath(byte[] storePoolId, byte[] storeBlobId)
        {
            if (storePoolId == null) throw new ArgumentNullException("storePoolId");
            if (storeBlobId == null) throw new ArgumentNullException("storeBlobId");

            // Extract the pool path and the blob Path
            //
            string poolLocation = ExtractPoolPath(storePoolId);

            Guid filenameGuid = new Guid(storeBlobId);
            string blobPath = filenameGuid.ToString() + ".rbsblob";
            
            string fullPath = Path.Combine(poolLocation, blobPath);
            DebugPrint.WriteLine(OperationType.BlobStoreReadBlob, LogLevel.Verbose,
                FileBlobStore.Resources.FileStoreLibraryPathTrace, fullPath);

            return fullPath;
        }

        /// <summary>
        /// Extracts the storePoolId and storeBlobId of a blob from the path.
        /// </summary>
        private
        void
        ExtractIds(string path, out byte[] storePoolId, out byte[] storeBlobId)
        {
            // Extract the filename without the file extension from the path. 
            // This is the blob id.
            //
            string fileName = Path.GetFileNameWithoutExtension(path);
            storeBlobId = new Guid(fileName).ToByteArray();

            // Strip out the last directory and turn it into a guid.
            // This is the pool id.
            //
            string filePath = Path.GetDirectoryName(path);
            filePath = filePath.Replace(m_BaseDirectory, "");
            filePath = filePath.Replace("\\", "");
            storePoolId = new Guid(filePath).ToByteArray();
        }

        /// <summary>
        /// Check the directory is accessible.
        /// Throw BlobStoreAccessDenied exception if this is not the case.
        /// </summary>
        static
        private
        void
        CheckAccessToDirectory(string directory)
        {
            try
            {
                Directory.GetDirectories(directory);
            }
            catch (System.UnauthorizedAccessException ex)
            {
                throw new BlobStoreException(BlobStoreExceptionCode.AccessDenied, ex, FileBlobStore.Resources.AccessDeniedError, directory);
            }
            catch (System.Security.SecurityException ex)
            {
                throw new BlobStoreException(BlobStoreExceptionCode.AccessDenied, ex, FileBlobStore.Resources.SecurityExceptionError, directory);
            }
            catch (System.IO.IOException ex)
            {
                throw new BlobStoreException(BlobStoreExceptionCode.OperationFailedAuthoritative, ex, FileBlobStore.Resources.OperationFailedAuthoritativeError);
            }
        }

        /// <summary>
        /// Check the file is accessible.
        /// Throw BlobStoreAccessDenied exception if this is not the case.
        /// </summary>
        static
        private
        void
        CheckAccessToFile(string file)
        {
            try
            {
                // There must be a better way than this.
                //
                using (FileStream fs = File.OpenRead(file))
                {
                }
            }
            catch (System.UnauthorizedAccessException ex)
            {
                throw new BlobStoreException(BlobStoreExceptionCode.AccessDenied, ex, FileBlobStore.Resources.AccessDeniedError, file);
            }
            catch (System.Security.SecurityException ex)
            {
                throw new BlobStoreException(BlobStoreExceptionCode.AccessDenied, ex, FileBlobStore.Resources.SecurityExceptionError, file);
            }
            catch (System.IO.IOException ex)
            {
                throw new BlobStoreException(BlobStoreExceptionCode.OperationFailedAuthoritative, ex, FileBlobStore.Resources.OperationFailedAuthoritativeError);
            }
        }

        #endregion

        //--------------------------------------------------------------------+
        //                                                                    |
        #region Internal fields

        private string m_BaseDirectory;

        private Impersonation m_Impersonation;

        /// <summary>
        /// This prevents issues if dispose is called multiple times.
        /// This is a common case in C# automatic finalizer chaining.
        /// </summary>
        private bool m_Disposed;

        private int m_PoolCapacity;

        #endregion
    }
}
